﻿// Copyright (c) 2008 Interact Software Ltd.

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 

// Dynamic Bitmap Generation for Silverlight
// =========================================
// See http://www.codeplex.com/SlDynamicBitmap for details.


namespace InteractSw.GenerateBitmap
{
    /// <summary>
    /// Represents a chunk in a PNG file.
    /// </summary>
    /// <remarks>
    /// A PNG file's top level structure is: a PNG signature followed by a sequence of chunks.
    /// Instances of this class represent a single chunk.
    /// 
    /// This class allows the chunk type (a 4-byte identifier for the chunk's purpose) and data
    /// to be provided. It presents the chunk contents in the binary form required by the PNG
    /// spec through the Read and Reset methods. The binary representation requires each chunk
    /// to be length-prefixed, and to end with a CRC32.
    /// </remarks>
    class PngChunk
    {
        /// <summary>
        /// 4-byte array containing the PNG chunk type.
        /// </summary>
        byte[] m_chunkType;


        /// <summary>
        /// 4-byte array containing the chunk length, in network order.
        /// </summary>
        byte[] m_length;


        /// <summary>
        /// The data for this chunk, or null if there is no data.
        /// </summary>
        byte[] m_data;


        /// <summary>
        /// The CRC for this chunk.
        /// </summary>
        byte[] m_crcBytes;


        /// <summary>
        /// The current position into the byte representation of this chunk for the
        /// next Read operation.
        /// </summary>
        int position;


        /// <summary>
        /// Construct a new PNG chunk of the specified chunk type.
        /// </summary>
        /// <param name="chunkType">4 byte array containing the chunk type.</param>
        /// <remarks>
        /// TODO: Should we force passing of data (allowing null) here, and then change
        /// SetData to UpdateData?
        /// </remarks>
        public PngChunk(byte[] chunkType)
        {
            m_chunkType = chunkType;
        }


        /// <summary>
        /// Provide the data for this chunk. (Not all chunks have data.)
        /// </summary>
        /// <param name="data">The data for this chunk.</param>
        public void SetData(byte[] data)
        {
            m_data = data;
            int length = m_data == null ? 0 : m_data.Length;
            m_length = NetworkOrderBitConverter.GetBytes((uint) length);
            uint crc = Crc32.UpdateCrc(0xffffffff, m_chunkType);
            if (m_data != null) { crc = Crc32.UpdateCrc(crc, m_data); }
            crc ^= 0xffffffff;
            m_crcBytes = NetworkOrderBitConverter.GetBytes(crc);
        }


        /// <summary>
        /// Read bytes from this chunk.
        /// </summary>
        /// <param name="buffer">Buffer into which to write bytes.</param>
        /// <param name="offset">Offset in buffer at which to start
        /// writing bytes.</param>
        /// <param name="count">Maximum number of bytes to fetch.</param>
        /// <returns>The number of bytes placed into the buffer. If the
        /// buffer is larger than the amount of remaining data, this will
        /// be less than count. Returns 0 to indicate that no data is left.
        /// </returns>
        public int Read(byte[] buffer, int offset, int count)
        {
            int dataLength = m_data == null ? 0 : m_data.Length;
            if (m_data == null)
            {
                m_length = new byte[4];
                m_crcBytes = NetworkOrderBitConverter.GetBytes(Crc32.Crc(m_chunkType));
            }

            int written = 0;
            while (written < count)
            {
                int amount = 1;
                if (position < 4)
                {
                    amount = CopyUtilities.WriteAsMuchDataAsPossible(buffer, offset + written, offset + count, m_length, position);
                }
                else if (position < 8)
                {
                    amount = CopyUtilities.WriteAsMuchDataAsPossible(buffer, offset + written, offset + count, m_chunkType, position - 4);
                }
                else if (position < (8 + dataLength))
                {
                    amount = CopyUtilities.WriteAsMuchDataAsPossible(buffer, offset + written, offset + count, m_data, position - 8);
                }
                else if (position < (12 + dataLength))
                {
                    amount = CopyUtilities.WriteAsMuchDataAsPossible(buffer, offset + written, offset + count, m_crcBytes, position - (8 + dataLength));
                }
                else
                {
                    return written;
                }

                position += amount;
                written += amount;
            }
            return count;
        }


        /// <summary>
        /// Resets the Read position.
        /// </summary>
        /// <remarks>
        /// Repeated calls to Read fetch data progressively from the chunk. Calling
        /// this method resets the position back to the start, so the data can be
        /// fetched again.
        /// </remarks>
        public void Rewind()
        {
            position = 0;
        }


        /// <summary>
        /// Total length of chunk's byte representation.
        /// </summary>
        public int Length
        {
            get
            {
                return 12 + (m_data == null ? 0 : m_data.Length);
            }
        }
    }
}
